package ln.gather.jsr.jpa.jndi;

import ln.gather.jsr.jpa.function.ThrowableAction;
import ln.gather.jsr.jpa.function.ThrowableFunction;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.naming.*;
import javax.servlet.ServletContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Java JNDI 实现
 */
public class ClassicComponentContext implements ComponentContext {

    public static final String CONTEXT_NAME = ClassicComponentContext.class.getName();

    private static String COMPONENT_ENV_CONTEXT_NAME = "java:comp/env";

    private static ServletContext servletContext;

    /**
     * Component Env Context
     */
    private Context envContext;

    private ClassLoader classLoader;

    private Map<String, Object> componentCache = new LinkedHashMap<>();

    private Map<Method, Object> preDestroyMethodCache = new LinkedHashMap<>();

    public static ClassicComponentContext getInstance() {
        return (ClassicComponentContext) servletContext.getAttribute(CONTEXT_NAME);
    }

    public void init(ServletContext servletContext) {
        ClassicComponentContext.servletContext = servletContext;
        servletContext.setAttribute(CONTEXT_NAME, this);
        this.init();
    }

    /**
     * 实例化组件
     */
    protected void instantiateComponents() {
        // 遍历获取所有的组件名称
        listAllComponentNames();
    }

    /**
     * 初始化组件
     */
    protected void initializeComponents() {
        componentCache.values().forEach(x -> initializeComponent(x));
    }

    /**
     * 初始化一个组件
     * 1. 注入阶段
     * 2. 初始阶段
     * 3. 销毁阶段
     */
    private void initializeComponent(Object component) {
        Class<?> componentClass = component.getClass();
        // 注入
        injectComponent(component, componentClass);
        // 查询候选方法 （非 static 和 无参数的方法）
        List<Method> candidateMethods = findCandidateMethods(componentClass);
        // 初始阶段（执行类中 @PostConstruct 的方法）
        processPostConstruct(component, candidateMethods);
        //本阶段处理， @PreDestroy方法元数据
        processPreDestroy(component, candidateMethods);
    }

    /**
     * 将组件标注了 @PreDestroy 的方法放置缓存中，当组件被销毁时去调用该方法
     *
     * @param component
     * @param candidateMethods
     */
    private void processPreDestroy(Object component, List<Method> candidateMethods) {
        candidateMethods.stream().filter(method -> !Modifier.isStatic(method.getModifiers()) && method.isAnnotationPresent(PreDestroy.class))
                .forEach(method -> preDestroyMethodCache.put(method, component));
    }

    private void processPreDestroy() {
        preDestroyMethodCache.keySet().forEach(method -> {
            // 移除集合中的对象，放置重复执行 @PreDestroy 方法
            Object component = preDestroyMethodCache.remove(method);
            // 执行组件的销毁方法
            ThrowableAction.execute(() -> method.invoke(component));
        });
    }


    /**
     * 初始化组件，执行组件中的 @PostConstruct 标注的方法
     */
    private void processPostConstruct(Object component, List<Method> candidateMethods) {
        candidateMethods.stream().filter(method -> method.isAnnotationPresent(PostConstruct.class))
                .forEach(method -> {
                    ThrowableAction.execute(() -> method.invoke(component));
                });
    }

    /**
     * 获取组件类中的候选方法
     * 非 static 以及 无参数的 方法
     */
    private List<Method> findCandidateMethods(Class<?> componentClass) {
        return Stream.of(componentClass.getDeclaredMethods())
                .filter(method -> !Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0)
                .collect(Collectors.toList());
    }


    /**
     * 注入一个实例
     */
    public void injectComponent(Object component) {
        injectComponent(component, component.getClass());
    }

    /**
     * 注入一个实例
     */
    protected void injectComponent(Object component, Class<?> componentClass) {
        Stream.of(componentClass.getDeclaredFields())
                .filter(filed -> {
                    int mods = filed.getModifiers();
                    return !Modifier.isStatic(mods) && filed.isAnnotationPresent(Resource.class);
                }).forEach(filed -> {
                    Resource annotation = filed.getAnnotation(Resource.class);
                    String resourceName = annotation.name();
                    Object injectedObject = lookupComponent(resourceName);
                    filed.setAccessible(true);
                    try {
                        filed.set(component, injectedObject);
                    } catch (IllegalAccessException e) {
                    }
                });

    }

    public <C> C lookupComponent(String name) {
        return executeInContext(context -> (C) context.lookup(name));
    }

    private List<String> listAllComponentNames() {
        return listComponentNames("/");
    }

    private List<String> listComponentNames(String name) {
        return executeInContext(context -> {
            NamingEnumeration<NameClassPair> e = executeInContext(context, ctx -> ctx.list(name), true);
            if (e == null) {
                // 当前JNDI名称下没有子节点
                return Collections.EMPTY_LIST;
            }
            List<String> fullNames = new LinkedList<>();
            while (e.hasMoreElements()) {
                NameClassPair element = e.nextElement();
                String className = element.getClassName();
                Class<?> targetClass = classLoader.loadClass(className);
                if (Context.class.isAssignableFrom(targetClass)) {
                    // 如果当前的目录是 Context 的实现类，则递归查找
                    fullNames.addAll(listComponentNames(element.getName()));
                } else {
                    // 否则，当前名称绑定目标类型的话，添加该名称到集合中
                    String fullName = name.startsWith("/") ? element.getName() : name + "/" + element.getName();
                    fullNames.add(fullName);
                }
            }
            return fullNames;
        });
    }

    /**
     * 在Context中执行，通过指定 ThrowableFunction 函数返回计算结果
     *
     * @param function
     * @param <R>
     * @return
     */
    protected <R> R executeInContext(ThrowableFunction<Context, R> function) {
        return executeInContext(function, false);
    }

    protected <R> R executeInContext(ThrowableFunction<Context, R> function, boolean ignoreException) {
        return executeInContext(this.envContext, function, ignoreException);
    }

    private <R> R executeInContext(Context context, ThrowableFunction<Context, R> function, boolean ignoreException) {
        R re = null;
        try {
            re = ThrowableFunction.execute(context, function);
        } catch (Throwable e) {
            if (ignoreException) {
                System.err.println(e);
            } else {
                throw new RuntimeException(e);
            }
        }
        return re;
    }

    private void initClassLoader() {
        // 获取当前 ServletContext（WebApp）ClassLoader
        this.classLoader = servletContext.getClassLoader();
    }

    /**
     * 初始化 环境上下换
     */
    private void initEnvContext() throws RuntimeException {
        if (this.envContext == null) {
            return;
        }
        Context context = null;
        try {
            context = new InitialContext();
            this.envContext = (Context) context.lookup(COMPONENT_ENV_CONTEXT_NAME);
        } catch (NamingException e) {
            throw new RuntimeException(e);
        } finally {
            close(context);
        }
    }


    /**
     * 生命周期：初始化
     */
    @Override
    public void init() {
        // 初始化 classloader
        initClassLoader();
        // 初始化上下文环境
        initEnvContext();
        // 实例化所有的组件
        instantiateComponents();
        // 初始化所有的组件
        initializeComponents();
        // 挂载关闭Hook
        registerShutdownHook();
    }

    private void registerShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> processPreDestroy()));
    }

    /**
     * 生命周期：销毁
     */
    @Override
    public void destroy() {
        // 执行组件的销毁方法
        processPreDestroy();
        // 清除缓存
        clearCache();
        // 关闭环境上下文
        closeEnvContext();
    }

    private void closeEnvContext() {
        close(this.envContext);
    }

    private void close(Context context) {
        if (context != null) {
            ThrowableAction.execute(context::close);
        }
    }

    private void clearCache() {
        componentCache.clear();
        preDestroyMethodCache.clear();
    }

    /**
     * 根据名字来获取组件实例对象
     *
     * @param name
     */
    @Override
    public <C> C getComponent(String name) {
        return (C) componentCache.get(name);
    }

    /**
     * 获取所有的组件实例名称
     */
    @Override
    public List<String> getComponentNames() {
        return new ArrayList<>(componentCache.keySet());
    }
}
